//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <wintypes.h>
#include <eltypes.h>
#include <winhack.h>
#include <elasys_server.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <filedisk.h>
#include <driver.h>

#define DEFAULTDISKNAME     L"disk.rom"
#define ERROR_ALREADY_EXISTS             183L
#define INFINITEIN            0xFFFFFFFF  // Infinite timeout

HANDLE g_hfilemutex;

typedef long HRESULT;

int FileDriveRW(unsigned long Position, void *pBuffer, int Size, int Flags);
HRESULT FileDriveOpenDisk(wchar_t *pName, UINT64 *pSize);
void FileDriveClose(void);

CFileDisk::CFileDisk()
{
    m_DiskSize = 0;
}

CFileDisk::~CFileDisk()
{
    if (g_hfilemutex) {
        CloseHandle(g_hfilemutex);
        g_hfilemutex = 0;
    }
    this->Release();
}

BOOL CFileDisk::initFileDisk(void)
{
    g_hfilemutex=CreateMutexW(NULL, FALSE, L"LOCAL_MUTEX_FILEDISK");
    if (g_hfilemutex == NULL) {
        if (GetLastError() == ERROR_ALREADY_EXISTS) {
            printf("mutex already exits - not created!\n");
            CloseHandle(g_hfilemutex);
            return FALSE;
        }
    }
    if (SUCCEEDED(OpenDisk(DEFAULTDISKNAME))) {
        return TRUE;
    }

    printf("There is no default FileDisk %S!\n", DEFAULTDISKNAME);
    return FALSE;
}

ECode CFileDisk::OpenDisk(wchar_t *pName)
{
    if (m_DiskSize != 0) {
        return E_ALREADY_EXIST;
    }
    ECode ec = NOERROR;
    wcscpy(m_wszFileName, pName);

    ec = (ECode)FileDriveOpenDisk(m_wszFileName, &m_DiskSize);
    if (ec == E_ACCESS_DENIED) {
        return E_ALREADY_EXIST;
    }
    return ec;
}

ECode CFileDisk::CloseDisk(void)
{
    FileDriveClose();
    m_DiskSize = 0;
    return NOERROR;
}

ECode CFileDisk::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    DWORD dw = WaitForSingleObject(g_hfilemutex, INFINITEIN);
    if (dw != WAIT_OBJECT_0) {
        ReleaseMutex(g_hfilemutex);
        wprintf(L"CFileDisk::Read: At file %d, line %d.\n", __FILE__, __LINE__);
        return E_NOT_OWNER;
    }

    if ((u64Offset + bytesToRead) > (Int32)(m_DiskSize)) {
        wprintf(L"****ReadDisk Out Of Boundary****\n");
        ReleaseMutex(g_hfilemutex);
        return E_INVALID_OPERATION;
    }

    int ret;

    assert((bytesToRead & 0x1ff) == 0);
    if (pBuffer == NULL) {
        return E_INVALID_ARGUMENT;
    }
    ret = FileDriveRW((unsigned long)u64Offset, pBuffer->GetPayload(), bytesToRead, 0);
//    printf("======================\n");
//    printf("Read to file disk %u byte.\n", buffer.GetUsed());
//    printf("======================\n");

    if (ret == (int)bytesToRead) {
        if (pBuffer == NULL) {
            return E_INVALID_ARGUMENT;
        }
        pBuffer->SetUsed(ret);
        ReleaseMutex(g_hfilemutex);
        return NOERROR;
    }
    ReleaseMutex(g_hfilemutex);
    wprintf(L"CFileDisk::Read: At file %d, line %d.\n", __FILE__, __LINE__);
    return E_INVALID_OPERATION;
}

ECode CFileDisk::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    DWORD dw = WaitForSingleObject(g_hfilemutex, INFINITEIN);
    if (dw != WAIT_OBJECT_0) {
        ReleaseMutex(g_hfilemutex);
        wprintf(L"CFileDisk::Write: At file %d, line %d.\n", __FILE__, __LINE__);
        return E_NOT_OWNER;
    }
    if ((u64Offset + buffer.GetUsed()) > (Int32)(m_DiskSize)) {
        wprintf(L"****WriteAt Out Of Boundary****\n");
        *pBytesWritten = 0;
        return E_INVALID_OPERATION;
    }

    int ret;
//    printf("======================\n");
//    printf("Write to file disk %u byte.\n", buffer.GetUsed());
//    printf("======================\n");
    assert((buffer.GetUsed() & 0x1ff) == 0);
    ret = FileDriveRW((unsigned long)u64Offset, buffer.GetPayload(), buffer.GetUsed(), 1);
    if (ret == buffer.GetUsed()) {
        if (pBytesWritten) *pBytesWritten = ret;
        ReleaseMutex(g_hfilemutex);
        return NOERROR;
    }
    ReleaseMutex(g_hfilemutex);
    wprintf(L"CFileDisk::Write: At file %d, line %d.\n", __FILE__, __LINE__);
    return E_INVALID_OPERATION;
}

ECode CFileDisk::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec = E_INVALID_ARGUMENT;
    switch (nControlCode) {
        case FILEDISK_CONTROL_OPEN:{
            if (inBuffer.IsNullOrEmpty()) {
                ec = OpenDisk(DEFAULTDISKNAME);
            }
            else
                ec = (ECode)OpenDisk((wchar_t *)inBuffer.GetPayload());
            if (FAILED(ec)) {
                break;
            }
            UINT sdData = 0xC0000001;//Insert
            EventNotify(EVENT_MiniSD, 0, &sdData);
            break;
        }
        case FILEDISK_CONTROL_CLOSE:{
            ec = CloseDisk();//BUGBUG: Can Remove disk before FS Cache flush?
            UINT sdData = 0xC0000002;//Remove
            EventNotify(EVENT_MiniSD, 0, &sdData);
            break;
        }
        case FILEDISK_CONTROL_GETSIZE:
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            pOutBuffer->Copy((Byte *)&m_DiskSize, sizeof(UINT64 *));
            ec = NOERROR;
            break;
        default:
            break;
    }
    return ec;
}

EXTERN IDeviceDriver * CDECL CreateDisk(uint_t uDeviceNo, void *pvParameter)
{
    CFileDisk *pFileDisk = new CFileDisk();
    pFileDisk->initFileDisk();

    pFileDisk->AddRef();
    return pFileDisk;
}
